/*
 * Huawei HiNIC PCI Express Linux driver
 * Copyright(c) 2017 Huawei Technologies Co., Ltd
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 */
#define pr_fmt(fmt) KBUILD_MODNAME ": [NIC]" fmt
#include <net/xfrm.h>
#include <net/esp.h>
#include <crypto/aead.h>

#include "ossl_knl.h"
#ifdef CONFIG_XFRM_OFFLOAD
#include "hinic3_hw.h"
#include "hinic3_nic.h"
#include "hisec_cmd.h"
#include "hisec_alg.h"

const char *crypt_alg_name[HISEC_CRYPTO_ALG_MAX] =
   {
       "rfc4106(gcm(aes))", "cbc(aes)", "rfc3686(ctr(aes))",
       "hmac(sha1)", "hmac(sha256)", "hmac(sha384)",
       "hmac(sha512)"
   };

int hisec_get_aead_gcm_info(void *hwdev, struct xfrm_state *xs, struct hisec_ipsec_alg_info *gcm_alg)
{
    struct hinic3_nic_io *nic_io = NULL;
    u16 key_len_bit, key_len_byte;

    if (!hwdev || !xs)
        return -EPERM;

    nic_io = hinic3_get_service_adapter(hwdev, SERVICE_T_NIC);

    gcm_alg->cipher_alg_sel = HISEC_CRYPTO_CIPHER_ALG_AES;
    gcm_alg->auth_type = HISEC_CRYPTO_AUTH_TYPE_GCM;
    gcm_alg->cipher_type = HISEC_CRYPTO_CIPHER_TYPE_GCM;
    gcm_alg->esp_iv_size = HISEC_IPSEC_ESP_IV_64BIT;

    /* The key bytes come down in a bigendian array of bytes, so
     * we don't need to do any byteswapping.
     * 160 accounts for 16 byte key and 4 byte salt  at end
     */
    key_len_bit = xs->aead->alg_key_len - AEAD_ALG_SALT_LEN_32BIT;
    key_len_byte = key_len_bit / AEAD_ALG_KEY_LEN_UNIT;
    gcm_alg->cipher_key_len_byte = key_len_byte;

    nic_info(nic_io->dev_hdl, "AES-GCM. [%s] alg_key_len:%d, alg_icv_len: %d, key_len_bit:%d, key_len_byte:%d\n",
        xs->aead->alg_name, xs->aead->alg_key_len, xs->aead->alg_icv_len, key_len_bit, key_len_byte);

    if (key_len_bit == AEAD_ALG_KEY_LEN_128BIT) {
        gcm_alg->cipher_key_len_sel = HISEC_CRYPTO_AES_KEY_LEN_128BIT;
        gcm_alg->auth_key_len = AEAD_ALG_AUTH_KEY_LEN_16B;
    } else if (key_len_bit == AEAD_ALG_KEY_LEN_192BIT) {
        gcm_alg->cipher_key_len_sel = HISEC_CRYPTO_AES_KEY_LEN_192BIT;
        gcm_alg->auth_key_len = AEAD_ALG_AUTH_KEY_LEN_24B;
    } else if (key_len_bit == AEAD_ALG_KEY_LEN_256BIT) {
        gcm_alg->cipher_key_len_sel = HISEC_CRYPTO_AES_KEY_LEN_256BIT;
        gcm_alg->auth_key_len = AEAD_ALG_AUTH_KEY_LEN_32B;
    } else {
        nic_err(nic_io->dev_hdl, "Invalid alg_ley_len: %d\n", xs->aead->alg_key_len);
        return -EINVAL;
    }

    switch (xs->aead->alg_icv_len) {
        case AEAD_ALG_ICV_LEN_64BIT:
        case AEAD_ALG_ICV_LEN_96BIT:
        case AEAD_ALG_ICV_LEN_128BIT:
            gcm_alg->icv_mac_len = (xs->aead->alg_icv_len / AEAD_ALG_KEY_LEN_UNIT);
            break;
        default:
            nic_err(nic_io->dev_hdl, "Invalid alg_icv_len: %d\n", xs->aead->alg_icv_len);
            return -EINVAL;
    }

    memcpy(gcm_alg->cipher_key, xs->aead->alg_key, key_len_byte);
    memcpy(&gcm_alg->salt, xs->aead->alg_key + key_len_byte, sizeof(gcm_alg->salt));

    return 0;
}

int hisec_get_aes_enc_info(void *hwdev, struct xfrm_state *xs, struct hisec_ipsec_alg_info *enc_alg)
{
    struct hinic3_nic_io *nic_io = NULL;
    u16 key_len_bit = 0, key_len_byte = 0;

    if (!hwdev || !xs)
        return -EPERM;

    nic_io = hinic3_get_service_adapter(hwdev, SERVICE_T_NIC);

    enc_alg->cipher_alg_sel = HISEC_CRYPTO_CIPHER_ALG_AES;
    if (!strcmp(xs->ealg->alg_name, crypt_alg_name[HISEC_CRYPTO_ALG_AES_CBC])) {
        enc_alg->cipher_type = HISEC_CRYPTO_CIPHER_TYPE_CBC;
        enc_alg->esp_iv_size = HISEC_IPSEC_ESP_IV_128BIT;
        key_len_bit = xs->ealg->alg_key_len;
        key_len_byte = key_len_bit / AEAD_ALG_KEY_LEN_UNIT;
        enc_alg->cipher_key_len_byte = key_len_byte;
    } else if (!strcmp(xs->ealg->alg_name, crypt_alg_name[HISEC_CRYPTO_ALG_AES_CTR])) {
        enc_alg->cipher_type = HISEC_CRYPTO_CIPHER_TYPE_CTR;
        enc_alg->esp_iv_size = HISEC_IPSEC_ESP_IV_64BIT;
        /* 160 accounts for 16 byte key and 4 byte salt  at end */
        key_len_bit = xs->ealg->alg_key_len - AEAD_ALG_SALT_LEN_32BIT;
        key_len_byte = key_len_bit / AEAD_ALG_KEY_LEN_UNIT;
        enc_alg->cipher_key_len_byte = key_len_byte;
        memcpy(&enc_alg->salt, xs->ealg->alg_key + key_len_byte, sizeof(enc_alg->salt));
    }

    nic_info(nic_io->dev_hdl, "AES-ENC. [%s] alg_key_len:%d, key_len_byte:%d\n", xs->ealg->alg_name,
        xs->ealg->alg_key_len, key_len_byte);

    if (key_len_bit == AEAD_ALG_KEY_LEN_128BIT) {
        enc_alg->cipher_key_len_sel = HISEC_CRYPTO_AES_KEY_LEN_128BIT;
    } else if (key_len_bit == AEAD_ALG_KEY_LEN_192BIT) {
        enc_alg->cipher_key_len_sel = HISEC_CRYPTO_AES_KEY_LEN_192BIT;
    } else if (key_len_bit == AEAD_ALG_KEY_LEN_256BIT) {
        enc_alg->cipher_key_len_sel = HISEC_CRYPTO_AES_KEY_LEN_256BIT;
    } else {
        nic_err(nic_io->dev_hdl, "Invalid alg_ley_len: %d\n", xs->ealg->alg_key_len);
        return -EINVAL;
    }

    memcpy(enc_alg->cipher_key, xs->ealg->alg_key, key_len_byte);

    return 0;
}

int hisec_get_auth_hmac_info(void *hwdev, struct xfrm_state *xs, struct hisec_ipsec_alg_info *hmac_alg)
{
    struct hinic3_nic_io *nic_io = NULL;
    u16 key_len_bit, key_len_byte, icv_trunc_len;

    if (!hwdev || !xs)
        return -EPERM;

    nic_io = hinic3_get_service_adapter(hwdev, SERVICE_T_NIC);

    hmac_alg->auth_type = HISEC_CRYPTO_AUTH_TYPE_HMAC;

    if ((xs->aalg->alg_trunc_len % AEAD_ALG_ICV_LEN_32BIT) != 0) {
        nic_err(nic_io->dev_hdl, "Invalid alg_trunc_len: %d\n", xs->aalg->alg_trunc_len);
        return -EINVAL;
    }

    key_len_bit = xs->aalg->alg_key_len;
    key_len_byte = key_len_bit / AEAD_ALG_KEY_LEN_UNIT;
    icv_trunc_len = xs->aalg->alg_trunc_len / AEAD_ALG_KEY_LEN_UNIT;
    hmac_alg->auth_key_len = key_len_byte;

    nic_info(nic_io->dev_hdl, "AUTH-HMAC. [%s] alg_key_len:%d, alg_trunc_len:%d, key_len_byte:%d\n", xs->aalg->alg_name,
        xs->aalg->alg_key_len, xs->aalg->alg_trunc_len, key_len_byte);

    hmac_alg->icv_mac_len = icv_trunc_len;
    if (!strcmp(xs->aalg->alg_name, crypt_alg_name[HISEC_CRYPTO_ALG_HMAC_SHA1])) {
        hmac_alg->sha2_alg_sel = HISEC_CRYPTO_HMAC_SHA1;
    } else if (!strcmp(xs->aalg->alg_name, crypt_alg_name[HISEC_CRYPTO_ALG_HMAC_SHA256])) {
        hmac_alg->sha2_alg_sel = HISEC_CRYPTO_HMAC_SHA256;
    } else if (!strcmp(xs->aalg->alg_name, crypt_alg_name[HISEC_CRYPTO_ALG_HMAC_SHA384])) {
        hmac_alg->sha2_alg_sel = HISEC_CRYPTO_HMAC_SHA512_384;
    } else if (!strcmp(xs->aalg->alg_name, crypt_alg_name[HISEC_CRYPTO_ALG_HMAC_SHA512])) {
        /* this is for netstack support sha512 */
        hmac_alg->sha2_alg_sel = HISEC_CRYPTO_HMAC_SHA512_512;
    }

    memcpy(hmac_alg->auth_key, xs->aalg->alg_key, key_len_byte);

    return 0;
}

#endif
